package com.hwd.gmall.common.aspect;

import com.alibaba.fastjson.JSONObject;
import com.hwd.gmall.common.constant.CacheConst;
import com.hwd.gmall.common.constant.RedisConst;
import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;

/**
 * 缓存切面类
 *
 * @author 黄伟东/Victor
 * @date 2022/4/23 19:50
 */
@Component
@Aspect
public class GmallCacheAspect {

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Resource
    private RedissonClient redissonClient;

    @Around("@annotation(com.hwd.gmall.common.aspect.HwdCache)")
    public Object cacheAroundAdvice(ProceedingJoinPoint point) {

        // 返回结果初始化
        Object result = null;
        try {
            // 获取方法的入参
            Object[] args = point.getArgs();
            // 获取方法的签名
            MethodSignature signature = (MethodSignature) point.getSignature();
            // 通过方法签名获取方法上的注解
            HwdCache gmallCache = signature.getMethod().getAnnotation(HwdCache.class);
            // 前缀
            String prefix = gmallCache.prefix();
            // 拼接数据的key=getSkuInfo:[1]
            String key = prefix + Arrays.asList(args);

            // 获取缓存数据，若有值，则转换为需要的返回结果类型返回，否则返回空
            result = cacheHit(signature, key);

            // 缓存有数据
            if (result != null) {
                return result;
            }

            // 初始化分布式锁
            RLock lock = redissonClient.getLock(key + ":lock");
            // 尝试加锁
            if (lock.tryLock(CacheConst.LOCK_WAIT_TIME, CacheConst.LOCK_LEASE_TIME, TimeUnit.SECONDS)) {
                try {
                    try {
                        // 执行方法，查询数据库
                        result = point.proceed(point.getArgs());
                        // 数据库中没有数据，缓存空，并设置 5 分钟过期时间（解决缓存穿透问题）防止缓存穿透
                        if (null == result) {
                            // 并把结果放入缓存
                            this.redisTemplate.opsForValue().set(key, JSONObject.toJSONString(new Object()),
                                    RedisConst.KEY_TIMEOUT_FOR_NULL_DATA,TimeUnit.SECONDS);
                            return null;
                        }
                    } catch (Throwable throwable) {
                        throwable.printStackTrace();
                    }
                    // 数据库中有数据，把结果放入缓存
                    this.redisTemplate.opsForValue().set(key, JSONObject.toJSONString(result),
                            RedisConst.KEY_TIMEOUT_FOR_DATA,TimeUnit.SECONDS);
                    return result;
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    // 释放锁
                    lock.unlock();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 获取缓存数据
     *
     * @param signature 方法签名
     * @param key       数据在redis中的key
     * @return java.lang.Object
     */
    private Object cacheHit(MethodSignature signature, String key) {
        // 1. 查询缓存
        String cache = (String) redisTemplate.opsForValue().get(key);
        if (StringUtils.isNotBlank(cache)) {
            // 有，则反序列化，直接返回
            // 获取方法返回类型
            Class<?> returnType = signature.getReturnType();
            // 不能使用parseArray<cache, T>，因为不知道List<T>中的泛型
            return JSONObject.parseObject(cache, returnType);
        }
        return null;
    }

}
